#include "std/debug.h"
#include "arch/hw_kb.h"
#include "arch/inout.h"

static unsigned char kb_caps_map[][84] = 
{
	{
		0 ,27 ,'1','2','3','4','5','6','7','8','9','0',
		'-','=',127, 9 ,'q','w','e','r','t','y','u','i',
		'o','p','[',']', 10, 0 ,'a','s','d','f','g','h',
		'j','k','l',';','\'','`',0 ,'\\','z','x','c','v',
		'b','n','m',',','.','/', 0 , 0 , 0 ,' ', 0 , 0 ,
		0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
		0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
	},
	{
		0 ,27 ,'1','2','3','4','5','6','7','8','9','0',
		'-','=',127, 9 ,'Q','W','E','R','T','Y','U','I',
		'O','P','{','}', 10, 0 ,'A','S','D','F','G','H',
		'J','K','L',':','"','`', 0 ,'\\','Z','X','C','V',
		'B','N','M',',','.','/', 0 , 0 , 0 ,' ', 0 , 0 ,
		0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
		0 , 0 , 0 ,'0', 0 , 0 , 0 ,'+', 0 , 0 , 0 , 0 ,
	},
};

static unsigned char kb_shift_map[][84] =
{
	{
		0 ,27 ,'!','@','#','$','%','^','&','*','(',')',
		'_','+',127, 9 ,'q','w','e','r','t','y','u','i',
		'o','p','{','}', 10, 0 ,'a','s','d','f','g','h',
		'j','k','l',':','"','~', 0 ,'|','z','x','c','v',
		'b','n','m','<','>','?', 0 , 0 , 0 ,' ', 0 , 0 ,
		0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
		0 , 0 , 0 ,'-', 0 , 0 , 0 ,'+', 0 , 0 , 0 , 0 ,
	},
	{
		0 ,27 ,'!','@','#','$','%','^','&','*','(',')',
		'_','+',127, 9 ,'Q','W','E','R','T','Y','U','I',
		'O','P','{','}', 10, 0 ,'A','S','D','F','G','H',
		'J','K','L',':','"','~', 0 ,'|','Z','X','C','V',
		'B','N','M','<','>','?', 0 , 0 , 0 ,' ', 0 , 0 ,
		0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
		0 , 0 , 0 ,'-', 0 , 0 , 0 ,'+', 0 , 0 , 0 , 0 ,
	},
};

#define KB_EXT_SCANCODE_CNT		(18)

unsigned char kb_ext_scancode_tbl[][2] = {
	{ 0x1C, KEY_KEYPAD_ENTER },
	{ 0x1D, KEY_RIGHT_CTRL },
	{ 0x35, KEY_KEYPAD_SLASH },
	{ 0x38, KEY_RIGHT_ALT },
	{ 0x47, KEY_HOME },
	{ 0x48, KEY_CUR_UP },
	{ 0x49, KEY_PAGE_UP },
	{ 0x4B, KEY_CUR_LEFT },
	{ 0x4D, KEY_CUR_RIGHT },
	{ 0x4F, KEY_END },
	{ 0x50, KEY_CUR_DOWN },
	{ 0x51, KEY_PAGE_DOWN },
	{ 0x52, KEY_INSERT },
	{ 0x53, KEY_DEL },
	{ 0x5B, KEY_WIN },
	{ 0x5E, KEY_ACPI_POWER },
	{ 0x5F, KEY_ACPI_SLEEP },
	{ 0x63, KEY_ACPI_WAKE }
};

/*
0xE0, 0x2A, 0xE0, 0x37	print screen pressed
0xE0, 0xB7, 0xE0, 0xAA	print screen released
0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5	pause pressed
*/

static unsigned int get_0xE0_scancode(unsigned int code)
{
	unsigned int mid;
	unsigned int rel = 0;
	unsigned int start = 0;
	unsigned int end = KB_EXT_SCANCODE_CNT;

	if (code >= 128)
	{
		code -= 128;
		rel = (1 << 16);
	}

	while (start <= end)
	{
		unsigned char m;
		mid = (start + end) / 2;
		m = kb_ext_scancode_tbl[mid][0];
		if (((unsigned char)code) < m) end = mid - 1;
		else if (((unsigned char)code) > m) start = mid + 1;
		else return ((unsigned int)kb_ext_scancode_tbl[mid][1]) | rel;
	}
	return 0;
}

#define E0_START		(1)
#define E1_START		(6)
#define E0E1_MAX_STEP	(10)

static unsigned int handle_0xE0E1_scancode(unsigned int code, unsigned int step)
{
	unsigned char *t;
	unsigned int ret;
	static unsigned char t1[] = { 0x2A, 2, 0xB7, 3, 0 };
	static unsigned char t2[] = { 0xE0, 4, 0 };
	static unsigned char t3[] = { 0xE0, 5, 0 };
	static unsigned char t4[] = { 0x37, 0, 0, KEY_PRNT_SCR, 0 };
	static unsigned char t5[] = { 0xAA, 0, 0, KEY_PRNT_SCR, 1 };
	static unsigned char t6[] = { 0x1D, 7, 0 };
	static unsigned char t7[] = { 0x45, 8, 0 };
	static unsigned char t8[] = { 0xE1, 9, 0 };
	static unsigned char t9[] = { 0x9D, 10, 0 };
	static unsigned char t10[] = { 0xC5, 0, 0, KEY_PAUSE, 1 };
	static unsigned char* stbl[] = { NULL, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10 };

	if (step == 1)
	{
		ret = get_0xE0_scancode(code);
		if (ret) return ret;
	}

	if (step > E0E1_MAX_STEP) return 0;
	for (t = stbl[step]; *t; t += 2)
	{
		if (*t == code)
		{
			ret = (((unsigned int)t[1]) << 24);			// next step
			if (t[1] == 0)
			{
				kassert(t[2] == 0);
				ret |= (unsigned int)t[3];				// scancode
				ret |= (((unsigned int)t[4]) << 16);	// press or release
			}
			return ret;
		}
	}
	return 0;
}

#define IS_UPPER(x)		((x) >= 'A' && (x) <= 'Z')
#define IS_LETTER(x)	((x) >= 'a' && (x) <= 'z')

static void hw_kb_wait(void)
{
	unsigned short retries = 0;
	while (--retries && inb(0x64) & 0x02);
}

static void hw_kb_ack(void)
{
	unsigned short retries = 0;
	while (--retries && inb(0x60) != 0xfa);
}

static unsigned char leds = 0;
static unsigned char key_lock_stat = 0;
static unsigned char key_stat[(KEY_MAX + 7) & ~7] = {0};

#define KEY_LOCK_STAT_CAPSLOCK		(1)
#define KEY_LOCK_STAT_NUMLOCK		(2)

#define capslocked()	(leds & LED_CAPSLOCK)

static int kb_test_key_stat(unsigned int keycode) {
	return (key_stat[keycode >> 3] & (1 << (keycode & 7)));
}

static void kb_set_key_stat(unsigned int keycode) {
	key_stat[keycode >> 3] |= (1 << (keycode & 7));
}

static void kb_clear_key_stat(unsigned int keycode) {
	key_stat[keycode >> 3] &= ~(1 << (keycode & 7));
}

static void hw_kb_set_leds(void)
{
	hw_kb_wait();
	outb(0xED, 0x60);
	hw_kb_wait();
	outb(leds, 0x60);
}

void kb_update_leds(unsigned int code)
{
	unsigned int sc = KB_SCANCODE(code);
	if (!(code & HW_KEY_FLAG_PRESS))
		return;

	if (KEY_CAPSLOCK == sc)
		leds ^= LED_CAPSLOCK;
	else if (KEY_NUMLOCK == sc)
		leds ^= LED_NUMLOCK;
	else if (KEY_SCROLLLOCK == sc)
		leds ^= LED_SLOCK;
	else return;
	hw_kb_set_leds();
}

unsigned int hw_kb_int_read_key(void)
{
	unsigned char key = inb(0x60);
	unsigned char val = inb(0x61);

	outb((unsigned char)(val | 0x80), 0x61);
	outb(val, 0x61);
	return key;
}

static unsigned int kb_init_key_stat(void)
{
	unsigned int ret = 0;
	if (kb_test_key_stat(KEY_LEFT_ALT) || kb_test_key_stat(KEY_RIGHT_ALT))
		ret |= HW_KEY_FLAG_ALT;
	if (kb_test_key_stat(KEY_LEFT_CTRL) || kb_test_key_stat(KEY_RIGHT_CTRL))
		ret |= HW_KEY_FLAG_CTRL;
	if (kb_test_key_stat(KEY_LEFT_SHFT) || kb_test_key_stat(KEY_RIGHT_SHFT))
		ret |= HW_KEY_FLAG_SHIFT;
	return ret;
}

static unsigned int kb_update_ascii(unsigned int keycode)
{
	int upper = 0;
	unsigned int ascii;
	if (keycode > 0x53) return keycode << 8;

	if (capslocked()) upper = !upper;
	if (kb_test_key_stat(KEY_LEFT_SHFT) || kb_test_key_stat(KEY_RIGHT_SHFT))
	{
		upper = !upper;
		ascii = kb_shift_map[upper][keycode];
	}
	else ascii = kb_caps_map[upper][keycode];
	return (keycode << 8) | ascii;
}

unsigned int kb_translate_key(unsigned int sb)
{
	static int _0xE0E1 = 0;
	unsigned int keycode = 0;
	unsigned int flags = kb_init_key_stat();

	if (_0xE0E1)
	{
		keycode = handle_0xE0E1_scancode(sb, _0xE0E1);
		_0xE0E1 = (keycode >> 24);
		if (!keycode) return 0;
		if (!_0xE0E1 && keycode & (1 << 16))		// action == release?
			keycode &= 0xFFFF, flags |= HW_KEY_FLAG_RELEASE;
	}
	else
	{
		if (0xE0 == sb) { _0xE0E1 = E0_START; return 0; }
		else if (0xE1 == sb) { _0xE0E1 = E1_START; return 0; }

		if (sb >= 128)
		{
			sb -= 128;
			flags |= HW_KEY_FLAG_RELEASE;
		}

		if (!sb) return 0;
		if (sb < 0x54) keycode = sb;
		else if (sb == 0x57) keycode = KEY_F11;
		else if (sb == 0x58) keycode = KEY_F12;
		else return 0;
	}

	if (_0xE0E1) return 0;

	if (!(flags & HW_KEY_FLAG_RELEASE))
	{
		if (kb_test_key_stat(keycode))
			flags |= HW_KEY_FLAG_REPEAT;
		else
		{
			flags |= HW_KEY_FLAG_PRESS;
			kb_set_key_stat(keycode);
		}
	}
	else kb_clear_key_stat(keycode);
	return flags | kb_update_ascii(keycode);
}

#ifdef TEST_DBG_WIN32

void hw_kb_unit_test(void)
{
	unsigned int ret;
	ret = kb_translate_key(0x00);
	//---
	ret = kb_translate_key(0x01);
	//---
	ret = kb_translate_key(0xE0);
	ret = kb_translate_key(0x52);
	//---
	ret = kb_translate_key(0xE0);
	ret = kb_translate_key(0xD2);
	//---
	ret = kb_translate_key(0xE0);
	ret = kb_translate_key(0x00);
	//---
	ret = kb_translate_key(0xE0);
	ret = kb_translate_key(0xB7);
	ret = kb_translate_key(0xE0);
	ret = kb_translate_key(0xAA);
	//---
	ret = kb_translate_key(0xE0);
	ret = kb_translate_key(0xB9);
	// 0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5
	ret = kb_translate_key(0xE1);
	ret = kb_translate_key(0x1D);
	ret = kb_translate_key(0x45);
	ret = kb_translate_key(0xE1);
	ret = kb_translate_key(0x9D);
	ret = kb_translate_key(0xC5);
}

#endif

/* EOF */
